#!/usr/local/bin/python2.7

import sys
import SocketServer
import socket
import string
import Queue
import threading
import thread
import time
sys.path.insert(0, '/usr/share/pyshared/daemon')
import daemon

# This is version 10 of the server.
# This version introduces a new class to handle data transactions more
# transparently and with less UDP cross talk.

_debug_ = True # Change to false if not debugging! duh!
_isdaemon_ = False # Change to true if not debugging! duh!

inMsgPool = Queue.Queue()
udpMsgPool = Queue.Queue()

CommandList = set(["LIST","KILL","STOP","CMD",
                   "HELLO","BYE","ON","OFF",
                   "STATUS","REMON","REMOFF",
                   "PING","LAMPON","LAMPOFF",
                   "CLEAR","LMPTEST","E","S",
                   "0","1","2","3","F","OK","PONG",
                   "ERROR","UNKNOWN","TOGREM","SRVSTAT"])
NameList = set()
AliasList = dict()
AddressList = dict()

lock = threading.Lock()
count = [0]

# This class is used in the msg pools to handle data transactions smoothly.
class Message(object):
    def __init__(self, arg):
        # the members of this dict are: {isCmd, cmdString, target, sender}
        self.isCmd = None
        self.prefix = None
        self.cmdString = None
        self.target = None
        self.name = None
        self.sender = None
        self.reply = None
        self.digested = False

        if('isCmd' in arg):
            self.isCmd = arg['isCmd']
        if('prefix' in arg):
            self.prefix = arg['prefix']
        if('cmdString' in arg):
            self.cmdString = arg['cmdString']
        if('target' in arg):
            self.target = arg['target']
            self.name = getName(self.target)
        if('sender' in arg):
            self.sender = arg['sender']

    def GetIsCmd(self):
        if(self.isCmd is not None):
            return self.isCmd
        else:
            return False

    def SetIsCmd(self, isCmd):
        self.isCmd = x

    def GetPrefix(self):
        if(self.prefix is not None):
            return self.prefix
        else:
            return ""

    def SetPrefix(self, prefix):
        self.prefix = prefix

    def GetCmd(self):
        if(self.cmdString is not None):
            return self.cmdString
        else:
            return ""

    def SetCmd(self, cmd):
        self.cmdStr = cmd

    def GetTarget(self):
        if(self.target is not None):
            return self.target
        else:
            return ""

    def SetTarget(self, target):
        self.target = target
        self.name = getName(target)

    def GetName(self):
        if self.name is not None:
            return self.name
        else:
            return "XXX"

    def GetSender(self):
        return self.sender

    def SetSender(self, sender):
         self.sender = sender

    def GetReply(self):
        return self.reply

    def SetReply(self, reply):
        self.digested = True
        self.reply = reply

    def GetDigested(self):
        return self.digested

    def SetDigested(self, digested):
        self.digested = digested

    IsCmd = property(GetIsCmd, SetIsCmd)
    Prefix = property(GetPrefix, SetPrefix)
    Cmd = property(GetCmd, SetCmd)
    Target = property(GetTarget, SetTarget)
    Sender = property(GetSender, SetSender)
    Name = property(GetName)
    Reply = property(GetReply, SetReply)
    Digested = property(GetDigested, SetDigested)

    @staticmethod
    def NewMessage(data, socket):
        data = process(data)
        if(len(data) == 1):
            return Message({'isCmd':False, 'sender':socket, 'cmdString':data[0]})
        elif(len(data) == 2):
            return Message({'isCmd':False, 'sender':socket, 'target':data[0], 'cmdString':data[1]})
        elif(len(data) == 3):
            return Message({'isCmd':True, 'prefix':data[0], 'sender':socket, 'target':data[1], 'cmdString':data[2]})
        else:
            return None


def process(arg):
    # a message has a typical format of <prefix:alias:command>
    msg = str(arg).strip()
    if msg.startswith("<") and msg.endswith(">"):
        msg = msg.strip("<>")
        msg = msg.split(":")
        return msg
    else:
        return None

def lookup(arg, theSet):
    if arg is not None:
        if str(arg) in theSet:
            return True
    else:
        return False

def clearUDPPool():
    if udpMsgPool.empty():
        return
    else:
        while not udpMsgPool.empty():
            udpMsgPool.get()
        return

def printSet(arg):
    val = str("[ ")
    for member in arg:
        val = val + str(member) + " "
    val = val + "]"
    return val

def getCount():
    lock.acquire()
    cnt = int(count[0])
    count[0] = count[0] + 1
    lock.release()
    return cnt

def getStatus(arg):
    myList = list()
    for client in arg:
        myList.append("{a}={b}".format(a=getAlias(client),b=client))
    myList = printSet(myList)
    status = "[ The server is running ]:{lst}".format(lst=myList)
    return status

def getIPs(arg):
    myList = list()
    for client in arg:
        myList.append("{a}={b}".format(a=getAlias(client),b=arg[client]))
    myList = printSet(myList)
    status = "[ The connected clients are: ]:{lst}".format(lst=myList)
    return status

def removeAlias(arg):
    removeList = list()
    for alias in AliasList:
        if AliasList[alias] == arg:
            removeList.append(alias)
    for alias in removeList:
        del AliasList[alias]

def getName(arg):
    if str(arg) in AliasList:
        return AliasList[str(arg)]
    else:
        return None

def getAlias(arg):
    if arg is not None:
        for alias in AliasList:
            if AliasList[alias] == str(arg):
                return alias
    else:
        return None

class MyCleaner:
    def __init__(self):
        self.running = False

    def run(self):
        self.running = True
        threading.Thread(target=MyCleaner.purge, args=(self, None)).start()

    def stop(self):
        self.running = False

    def purge(self, args):
        if _debug_:
            print("Starting purge.")

        while self.running:
            time.sleep(60)
            if _debug_:
                    print("Purge.")
            MyTCPHandler.killer()

        if _debug_:
            print("Stopping purge.")

class MyTCPHandler(SocketServer.BaseRequestHandler):
    _txQueue = dict()

    def setup(self):
        self.name = ["CLNT{s}".format(s = getCount())]  # Set client name.
        NameList.add(self.name[0])                      # Add client name to name list.
        AddressList[self.name[0]] = self.client_address # Add client address to address list.
        self.txQueue = Queue.Queue()                    # Instatiate my txQueue.
        MyTCPHandler._txQueue[self.name[0]] = self.txQueue      # add my txQueue to the dictionary.
        if _debug_:
            print("Client {client} aka {name} just connected!".format(client = self.client_address, name = self.name[0]))
        self.reply = None                               # Add my reply var.
        self.killed = False                             # Im not dead.
        self.thread = threading.currentThread()
        threading.Thread(target=MyTCPHandler.handle_send, args=(self, None)).start() # Start Tx thread.
        threading.Timer(2.0, MyTCPHandler.greet, [self]).start() # Fire off greeting in 2 seconds.

    @staticmethod
    def SendMsg(msg):
        if MyTCPHandler._txQueue.get(msg.Name) is not None:
           MyTCPHandler._txQueue[msg.Name].put(msg)

    @staticmethod
    def KillAll():
        for name in MyTCPHandler._txQueue:
          MyTCPHandler._txQueue[name].put("<KILL>")

    @staticmethod
    def killer():
        killList = NameList - set(AliasList.viewvalues())
        if _debug_:
            print("Kill list: {l}".format(l=killList))
        if killList is not None:
            for doomed in killList:
                MyTCPHandler._txQueue[doomed].put("<KILL>")
                if _debug_:
                    print(doomed)
        return

    @staticmethod
    def Kill(msg):
        if MyTCPHandler._txQueue.get(msg.Name) is not None:
           MyTCPHandler._txQueue[msg.Name].put("<KILL>")

    def handle(self):
        try:
            while not self.killed :
                data = self.request.recv(1024)
                msg = process(data)
                myName = self.name[0]
                if _debug_:
                    print("TCP RX: Msg from {client} aka {name}: {mymsg}".format(client = self.client_address, name = myName, mymsg = msg))
                if msg is not None and not self.killed:
                    myMsg = msg[0]
                    if lookup(myMsg, CommandList):
                        if myMsg == "BYE":
                            MyTCPHandler._txQueue[myName].put("<KILL>")
                            self.killed = True
                            continue
                        elif myMsg == "HELLO":
                            if len(msg) == 2:
                                AliasList[str(msg[1])] = myName # Map name to alias upon greeting.
                        else:
                            if self.reply is not None:
                                reply = self.reply
                                reply.Reply = "<{a}:{m}>".format(m = myMsg, a = getAlias(myName))

                                inMsgPool.put(reply)
                                self.reply = None

        except Exception as ex:
            if _debug_:
                print("TCP Read Exception", ex)
            self.killed = True

    def handle_send(self, args):
        try:
            while not self.killed:
                myName = str(self.name[0])
                myAlias = "{0}={1}".format(getAlias(myName), myName)
                msg = self.txQueue.get() # block on queue read.

                if msg is None:
                    continue
#TODO: Clean up
# Ill mess with this if the queue sytem works!

                if type(msg) == Message:
                    if not msg.Digested:
                        if _debug_:
                            print("TCP TX: Msg for {0} aka {1}( {2} ): {3}".format(self.client_address, msg.Target , myName, msg.Cmd))
                        self.request.send(str.encode("<{0}>\r\n".format(msg.Cmd)))
                        self.reply = msg

                elif type(msg) == str:
                    if _debug_:
                        print("TCP TX: Msg for {0} aka {1} : {2}".format(self.client_address, myName, msg))
                    msg = process(msg)
                    if msg[0] == 'KILL':
                        if _debug_:
                            print("{name} just died.".format(name = myName))
                        self.killed = True
                        self.request.shutdown(socket.SHUT_RD)
                        continue

        except Exception as ex:
            if _debug_:
                print("TCP Send Exception", ex)
            self.killed = True
            self.request.shutdown(socket.SHUT_RD)

    def finish(self):
        try:
            if _debug_:
                print("Client {client} just disconnected!".format(client = self.client_address))
            MyTCPHandler._txQueue.pop(str(self.name[0]), None) # Remove my txQueue from dictionary
            AddressList.pop(str(self.name[0]), None)           # Remove my Address from dictionary
            NameList.discard(str(self.name[0]))
            removeAlias(str(self.name[0]))
            self.request.send(str.encode('<BYE>\r\n'))
            self.request.shutdown(socket.SHUT_WR)
        except Exception as ex:
            if _debug_:
                print("Finnish Exception", self.name, ex)
            else:
                pass

    def greet(self):
        try:
            self.request.send(str.encode('<HELLO>\r\n'))
        except:
            pass

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

class MyUDPHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        try:
            senderSocket = self.request[1]
            senderAddress = self.client_address
            command = bytes.decode(self.request[0]).strip()
            msg = Message.NewMessage(command, (senderSocket, senderAddress))

            if _debug_:
                print("\n")
                print("UDP RX: msg from {c}: {m}".format(c = msg.Sender[1], m = command))

            clearUDPPool()

            if msg is not None:
                if msg.IsCmd and msg.Name == 'XXX':
                    msg.Reply = "<{0}:???>".format(msg.Target)
                else:
                    inMsgPool.put(msg)
                    msg = udpMsgPool.get(True, 5)
            else:
                return

            if _debug_:
                print("UDP TX: msg to {c}: {m}".format(c = msg.Sender[1], m = msg.Reply))
                print("\n")
            msg.Sender[0].sendto(str.encode(msg.Reply), msg.Sender[1])
        except Exception as ex:
            if _debug_:
                print("UDP exception", ex)
            else:
                pass

class ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
    pass

def MainWork():
    userver = ThreadedUDPServer(('', 5000), MyUDPHandler)
    tserver = ThreadedTCPServer(('', 5001), MyTCPHandler)

    udp_server_thread = threading.Thread(target=userver.serve_forever)
    udp_server_thread.setDaemon(True)
    udp_server_thread.start()

    tcp_server_thread = threading.Thread(target=tserver.serve_forever)
    tcp_server_thread.setDaemon(True)
    tcp_server_thread.start()

    cleaner = MyCleaner()

    if _debug_:
        print("Starting up . . .")

    cleaner.run()

    while True:
        try:
            msg = inMsgPool.get()
            if _debug_:
                print("Main: +++ {0} {1} {2} {3} {4} +++ ".format(msg.Target, msg.Sender[1], msg.Cmd, msg.Reply, msg.Digested))
            if msg is not None:
                if msg.Digested:
                    udpMsgPool.put(msg)
                elif lookup(msg.Cmd, CommandList):
                    if msg.IsCmd:
                        if msg.Prefix == "CMD":
                            prefix = msg.Prefix
                            alias = msg.Target
                            cmd = msg.Cmd
                            if _debug_:
                                print("Main Command: [ {0} {1} {2} ]".format(prefix, alias, cmd))
                            name = msg.Name
                            MyTCPHandler.SendMsg(msg)
                        elif msg.Prefix == "SRV":
                            if msg.Cmd == "KILL":
                                MyTCPHandler.Kill(msg)
                    else:
                        if msg.Cmd == "LIST":
                            msg.Reply = getIPs(AddressList)
                            if _debug_:
                                print("LIST:" + msg.Reply)
                            udpMsgPool.put(msg)
                        elif msg.Cmd == 'STOP':
                            if _debug_:
                                print("STOP")
                            msg.Reply = "[ STOPPING SERVER ]"
                            udpMsgPool.put(msg)
                            break
                        elif msg.Cmd == "KILL":
                            if _debug_:
                                print("KILLING:\n")
                            msg.Reply = "[ KILLING CLIENTS ]"
                            udpMsgPool.put(msg)
                            MyTCPHandler.KillAll()
                        elif msg.Cmd == "SRVSTAT":
                            msg.Reply = getStatus(NameList)
                            if _debug_:
                                print("STATUS: " + msg.Reply)
                            udpMsgPool.put(msg)
                        else:
                            udpMsgPool.put(msg)
        except Exception as ex:
            if _debug_:
                print("Main exception: ", ex)
            else:
                pass

    tserver.shutdown()
    userver.shutdown()
    cleaner.stop()

    if _debug_:
        print("Shutting Down . . .")

if _isdaemon_:
    with daemon.DaemonContext():
        MainWork()
else:
    MainWork()
